采集之王[合].js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. /**
  2. * 强烈推荐静态分类。可以加快速度!!!
  3. * 不建议:
  4. * 传参 ?type=url&params=../json/采集.json
  5. * 建议:
  6. * 传参 ?type=url&params=../json/采集静态.json$1
  7. * 传参 ?type=url&params=../json/采集[zy]静态.json$1
  8. * 传参 ?type=url&params=../json/采集[密]静态.json$1
  9. * hipy-server支持@改名比如:
  10. * 传参 ?type=url&params=../json/采集静态.json$1@采王道长[合]
  11. * 传参 ?type=url&params=../json/采集[zy]静态.json$1@采王zy[密]
  12. * 传参 ?type=url&params=../json/采集[密]静态.json@采王成人[密]
  13. * [{"name":"暴风资源","url":"https://bfzyapi.com","parse_url":""},{"name":"飞刀资源","url":"http://www.feidaozy.com","parse_url":""},{"name":"黑木耳资源","url":"https://www.heimuer.tv","parse_url":""}]
  14. */
  15. globalThis.getRandomItem = function (items) {//从列表随机取出一个元素
  16. return items[Math.random() * items.length | 0];
  17. }
  18. var rule = {
  19. title: '采集之王[合]',
  20. author: '道长',
  21. version: '20240706 beta17',
  22. update_info: `
  23. 20240706:
  24. 1.静态json数据支持cate_excludes分类名称列表过滤无数据分类
  25. 2.更新采集分类生成器增加过滤筛选模式
  26. 20240705:
  27. 1.支持传参json后面增加$1 这样的额外标识,用于搜索结果精准匹配
  28. 2.支持传参json后面增加$1$1 这样的额外标识,用于强制获取搜索图片。$1$不显示图片。默认是搜索强制有图片的[已实现详情页请求使用批量]
  29. 3.修复二级数据无序匹配搜索列表图片的问题
  30. 4.修改搜索精准和图片显示额外参数间隔符从#变为$
  31. 20240703:
  32. 1.采集json支持"searchable": 0,用于搜索时排除这个源
  33. 20240604:
  34. 1.首页推荐取消硬控等待。增加随机推荐功能。
  35. 2.首页推荐新增更新日志查看功能
  36. 【特别说明】目前只支持标准json格式的采集站(资源站一般都提供xml和json两种接口,目前没有支持xml的想法,没什么必要)
  37. 有些资源站的json接口不是标准的/api.php/provide/vod/,需要自己在采集静态.json中编辑对应的api属性填写比如:/api.php/provide/vod/at/json/
  38. 有些资源站的采集数据是加密后的切片片段,可能需要采集站特定的解析接口,需要自己编辑json里的parse_url属性
  39. 资源站部分大分类下无数据很正常,可以自行编辑json里cate_exclude属性排除掉自己测试过无数据的分类(小程序无法自动识别,只能人工测好哪些分类无数据)
  40. `.trim(),
  41. host: '',
  42. homeTid: '', // 首页推荐。一般填写第一个资源站的想要的推荐分类的id.可以空
  43. homeUrl: '/api.php/provide/vod/?ac=detail&t={{rule.homeTid}}',
  44. detailUrl: '/api.php/provide/vod/?ac=detail&ids=fyid',
  45. searchUrl: '/api.php/provide/vod/?wd=**&pg=#TruePage##page=fypage',
  46. classUrl: '/api.php/provide/vod/',
  47. url: '/api.php/provide/vod/?ac=detail&pg=fypage&t=fyfilter',
  48. filter_url: '{{fl.类型}}',
  49. headers: {'User-Agent': 'MOBILE_UA'},
  50. timeout: 5000, // class_name: '电影&电视剧&综艺&动漫',
  51. limit: 20,
  52. search_limit: 5, // 搜索限制取前5个,可以注释掉,就不限制搜索
  53. searchable: 1,//是否启用全局搜索,
  54. quickSearch: 0,//是否启用快速搜索,
  55. filterable: 1,//是否启用分类筛选,
  56. play_parse: true,
  57. parse_url: '', // 这个参数暂时不起作用。聚合类的每个资源应该有自己独立的解析口。单独配置在采集.json里的parse_url有效
  58. search_match: false, // 搜索精准匹配
  59. search_pic: true, // 搜索强制需要图片
  60. // params: 'http://127.0.0.1:5707/files/json/%E9%87%87%E9%9B%86.json',
  61. // params: 'http://127.0.0.1:5707/files/json/采集静态.json$1',
  62. // params: 'http://127.0.0.1:5707/files/json/采集[zy]静态.json$1',
  63. // hostJs:$js.toString(()=>{
  64. //
  65. // }),
  66. 预处理: $js.toString(() => {
  67. function getClasses(item) {
  68. let classes = [];
  69. if (item.class_name && item.class_url) {
  70. if (!/&|电影|电视剧|综艺|动漫[\u4E00-\u9FA5]+/.test(item.class_name)) {
  71. try {
  72. item.class_name = ungzip(item.class_name)
  73. } catch (e) {
  74. log(`不识别的class_name导致gzip解码失败:${e}`)
  75. return classes
  76. }
  77. }
  78. let names = item.class_name.split('&');
  79. let urls = item.class_url.split('&');
  80. let cnt = Math.min(names.length, urls.length);
  81. for (let i = 0; i < cnt; i++) {
  82. classes.push({
  83. 'type_id': urls[i],
  84. 'type_name': names[i]
  85. });
  86. }
  87. }
  88. return classes
  89. }
  90. if (typeof (batchFetch) === 'function') {
  91. // 支持批量请求直接放飞自我。搜索限制最大线程数量16
  92. rule.search_limit = 16;
  93. log('当前程序支持批量请求[batchFetch],搜索限制已设置为16');
  94. }
  95. let _url = rule.params;
  96. log(`传入参数:${_url}`);
  97. if (_url && typeof (_url) === 'string' && /^(http|file)/.test(_url)) {
  98. if (_url.includes('$')) {
  99. let _url_params = _url.split('$');
  100. _url = _url_params[0];
  101. rule.search_match = !!(_url_params[1]);
  102. if (_url_params.length > 2) { // 强制图片
  103. rule.search_pic = !!(_url_params[2]);
  104. }
  105. }
  106. let html = request(_url);
  107. let json = JSON.parse(html);
  108. let _classes = [];
  109. rule.filter = {};
  110. rule.filter_def = {};
  111. json.forEach(it => {
  112. let _obj = {
  113. type_name: it.name,
  114. type_id: it.url,
  115. parse_url: it.parse_url || '',
  116. searchable: it.searchable !== 0,
  117. api: it.api || '',
  118. cate_exclude: it.cate_exclude || '',
  119. cate_excludes: it.cate_excludes || [],
  120. // class_name: it.class_name || '',
  121. // class_url: it.class_url || '',
  122. };
  123. _classes.push(_obj);
  124. try {
  125. let json1 = [];
  126. if (it.class_name && it.class_url) {
  127. json1 = getClasses(it);
  128. } else {
  129. json1 = JSON.parse(request(urljoin(_obj.type_id, _obj.api || rule.classUrl))).class;
  130. }
  131. if (_obj.cate_excludes && Array.isArray(_obj.cate_excludes) && _obj.cate_excludes.length > 0) {
  132. json1 = json1.filter(cl => !_obj.cate_excludes.includes(cl.type_name));
  133. } else if (_obj.cate_exclude) {
  134. json1 = json1.filter(cl => !new RegExp(_obj.cate_exclude, 'i').test(cl.type_name));
  135. }
  136. rule.filter[_obj.type_id] = [{
  137. "key": "类型", "name": "类型", "value": json1.map(i => {
  138. return {"n": i.type_name, 'v': i.type_id}
  139. })
  140. }];
  141. if (json1.length > 0) {
  142. rule.filter_def[it.url] = {"类型": json1[0].type_id};
  143. }
  144. } catch (e) {
  145. rule.filter[it.url] = [{"key": "类型", "name": "类型", "value": [{"n": "全部", "v": ""}]}];
  146. }
  147. });
  148. rule.classes = _classes;
  149. }
  150. }),
  151. // class_parse: $js.toString(() => {
  152. // let _url = rule.params;
  153. // if (_url && typeof (_url) === 'string' && _url.startsWith('http')) {
  154. // let html = request(_url);
  155. // let json = JSON.parse(html);
  156. // let _classes = [];
  157. // homeObj.filter = {};
  158. // rule.filter_def = {};
  159. // json.forEach(it => {
  160. // let _obj = {
  161. // type_name: it.name,
  162. // type_id: it.url,
  163. // parse_url: it.parse_url || '',
  164. // cate_exclude: it.cate_exclude || '',
  165. // };
  166. // _classes.push(_obj);
  167. // try {
  168. // let json1 = JSON.parse(request(urljoin(_obj.type_id, rule.classUrl))).class;
  169. // if (_obj.cate_exclude) {
  170. // json1 = json1.filter(cl => !new RegExp(_obj.cate_exclude, 'i').test(cl.type_name));
  171. // }
  172. // homeObj.filter[_obj.type_id] = [{
  173. // "key": "类型", "name": "类型", "value": json1.map(i => {
  174. // return {"n": i.type_name, 'v': i.type_id}
  175. // })
  176. // }];
  177. // if (json1.length > 0) {
  178. // rule.filter_def[it.url] = {"类型": json1[0].type_id};
  179. // }
  180. // } catch (e) {
  181. // homeObj.filter[it.url] = [{"key": "类型", "name": "类型", "value": [{"n": "全部", "v": ""}]}];
  182. // }
  183. // });
  184. // rule.classes = _classes;
  185. // input = _classes;
  186. // }
  187. // }),
  188. class_parse: $js.toString(() => {
  189. input = rule.classes;
  190. }),
  191. 推荐: $js.toString(() => {
  192. let update_info = [{
  193. vod_name: '更新日志',
  194. vod_id: 'update_info',
  195. vod_remarks: `版本:${rule.version}`,
  196. vod_pic: 'https://ghp.ci/https://raw.githubusercontent.com/ls125781003/hikerjsonK/main/img/logo.png'
  197. }];
  198. VODS = [];
  199. if (rule.classes) {
  200. let randomClass = getRandomItem(rule.classes);
  201. let _url = urljoin(randomClass.type_id, input);
  202. if (randomClass.api) {
  203. _url = _url.replace('/api.php/provide/vod/', randomClass.api)
  204. }
  205. try {
  206. let html = request(_url, {timeout: rule.timeout});
  207. let json = JSON.parse(html);
  208. VODS = json.list;
  209. VODS.forEach(it => {
  210. it.vod_id = randomClass.type_id + '$' + it.vod_id;
  211. it.vod_remarks = it.vod_remarks + '|' + randomClass.type_name;
  212. });
  213. } catch (e) {
  214. }
  215. }
  216. VODS = update_info.concat(VODS);
  217. }),
  218. 一级: $js.toString(() => {
  219. VODS = [];
  220. if (rule.classes) {
  221. // log(input);
  222. let _url = urljoin(MY_CATE, input);
  223. let current_vod = rule.classes.find(item => item.type_id === MY_CATE);
  224. if (current_vod && current_vod.api) {
  225. _url = _url.replace('/api.php/provide/vod/', current_vod.api)
  226. }
  227. let html = request(_url);
  228. let json = JSON.parse(html);
  229. VODS = json.list;
  230. VODS.forEach(it => {
  231. it.vod_id = MY_CATE + '$' + it.vod_id
  232. });
  233. }
  234. }),
  235. // 一级: 'json:list;vod_name;vod_pic;vod_remarks;vod_id;vod_play_from',
  236. 二级: $js.toString(() => {
  237. VOD = {};
  238. if (orId === 'update_info') {
  239. VOD = {
  240. vod_content: rule.update_info.trim(),
  241. vod_name: '更新日志',
  242. type_name: '更新日志',
  243. vod_pic: 'https://resource-cdn.tuxiaobei.com/video/FtWhs2mewX_7nEuE51_k6zvg6awl.png',
  244. vod_remarks: `版本:${rule.version}`,
  245. vod_play_from: '道长在线',
  246. // vod_play_url: '嗅探播放$https://resource-cdn.tuxiaobei.com/video/10/8f/108fc9d1ac3f69d29a738cdc097c9018.mp4',
  247. vod_play_url: '随机小视频$http://api.yujn.cn/api/zzxjj.php',
  248. };
  249. } else {
  250. if (rule.classes) {
  251. let _url = urljoin(fyclass, input);
  252. let current_vod = rule.classes.find(item => item.type_id === fyclass);
  253. if (current_vod && current_vod.api) {
  254. _url = _url.replace('/api.php/provide/vod/', current_vod.api)
  255. }
  256. let html = request(_url);
  257. let json = JSON.parse(html);
  258. let data = json.list;
  259. VOD = data[0];
  260. if (current_vod && current_vod.type_name) {
  261. VOD.vod_play_from = VOD.vod_play_from.split('$$$').map(it => current_vod.type_name + '|' + it).join('$$$')
  262. }
  263. }
  264. }
  265. }),
  266. 搜索: $js.toString(() => {
  267. VODS = [];
  268. if (rule.classes) {
  269. let canSearch = rule.classes.filter(it => it.searchable);
  270. let page = Number(MY_PAGE);
  271. page = (MY_PAGE - 1) % Math.ceil(canSearch.length / rule.search_limit) + 1;
  272. let truePage = Math.ceil(MY_PAGE / Math.ceil(canSearch.length / rule.search_limit));
  273. if (rule.search_limit) {
  274. let start = (page - 1) * rule.search_limit;
  275. let end = page * rule.search_limit;
  276. let t1 = new Date().getTime();
  277. let searchMode = typeof (batchFetch) === 'function' ? '批量' : '单个';
  278. log('start:' + start);
  279. log('end:' + end);
  280. log('搜索模式:' + searchMode);
  281. log('精准搜索:' + rule.search_match);
  282. log('强制获取图片:' + rule.search_pic);
  283. // log('t1:' + t1);
  284. if (start < canSearch.length) {
  285. let search_classes = canSearch.slice(start, end);
  286. let urls = [];
  287. search_classes.forEach(it => {
  288. let _url = urljoin(it.type_id, input);
  289. if (it.api) {
  290. _url = _url.replace('/api.php/provide/vod/', it.api)
  291. }
  292. _url = _url.replace("#TruePage#", "" + truePage);
  293. urls.push(_url);
  294. });
  295. let results_list = [];
  296. let results = [];
  297. if (typeof (batchFetch) === 'function') {
  298. let reqUrls = urls.map(it => {
  299. return {
  300. url: it,
  301. options: {timeout: rule.timeout}
  302. }
  303. });
  304. let rets = batchFetch(reqUrls);
  305. let detailUrls = [];
  306. let detailUrlCount = 0;
  307. rets.forEach((ret, idx) => {
  308. let it = search_classes[idx];
  309. if (ret) {
  310. try {
  311. let json = JSON.parse(ret);
  312. let data = json.list;
  313. data.forEach(i => {
  314. i.site_name = it.type_name;
  315. i.vod_id = it.type_id + '$' + i.vod_id;
  316. i.vod_remarks = i.vod_remarks + '|' + it.type_name;
  317. });
  318. if (rule.search_match) {
  319. data = data.filter(item => item.vod_name && (new RegExp(KEY, 'i')).test(item.vod_name))
  320. }
  321. if (data.length > 0) {
  322. if (rule.search_pic && !data[0].vod_pic) {
  323. log(`当前搜索站点【${it.type_name}】没图片,尝试访问二级去获取图片`);
  324. let detailUrl = urls[idx].split('wd=')[0] + 'ac=detail&ids=' + data.map(k => k.vod_id.split('$')[1]).join(',');
  325. detailUrls.push(detailUrl);
  326. results_list.push({
  327. data: data,
  328. has_pic: false,
  329. detailUrlCount: detailUrlCount
  330. });
  331. detailUrlCount++;
  332. // try {
  333. // let detailJson = JSON.parse(request(detailUrl));
  334. // data.forEach((d, _seq) => {
  335. // log('二级数据列表元素数:' + detailJson.list.length);
  336. // let detailVodPic = detailJson.list[_seq].vod_pic;
  337. // if (detailVodPic) {
  338. // Object.assign(d, {vod_pic: detailVodPic});
  339. // }
  340. // });
  341. // } catch (e) {
  342. // log(`强制获取网站${it.type_id}的搜索图片失败:${e.message}`);
  343. // }
  344. } else {
  345. results_list.push({data: data, has_pic: true});
  346. }
  347. // results = results.concat(data);
  348. }
  349. } catch (e) {
  350. log(`请求:${it.type_id}发生错误:${e.message}`)
  351. }
  352. }
  353. });
  354. // 构造请求二级的batchFetch列表
  355. let reqUrls2 = detailUrls.map(it => {
  356. return {
  357. url: it,
  358. options: {timeout: rule.timeout}
  359. }
  360. });
  361. let rets2 = reqUrls2.length > 0 ? batchFetch(reqUrls2) : [];
  362. for (let k = 0; k < results_list.length; k++) {
  363. let result_data = results_list[k].data;
  364. if (!results_list[k].has_pic) {
  365. try {
  366. let detailJson = JSON.parse(rets2[results_list[k].detailUrlCount]);
  367. log('二级数据列表元素数:' + detailJson.list.length);
  368. result_data.forEach((d, _seq) => {
  369. // let detailVodPic = detailJson.list[_seq].vod_pic;
  370. // log(detailJson);
  371. let detailVodPic = detailJson.list.find(vod => vod.vod_id.toString() === d.vod_id.split('$')[1]);
  372. if (detailVodPic) {
  373. Object.assign(d, {vod_pic: detailVodPic.vod_pic});
  374. }
  375. });
  376. } catch (e) {
  377. log(`强制获取网站${result_data[0].site_name}的搜索图片失败:${e.message}`);
  378. }
  379. }
  380. results = results.concat(result_data);
  381. }
  382. } else {
  383. urls.forEach((_url, idx) => {
  384. let it = search_classes[idx];
  385. try {
  386. let html = request(_url);
  387. let json = JSON.parse(html);
  388. let data = json.list;
  389. data.forEach(i => {
  390. i.vod_id = it.type_id + '$' + i.vod_id;
  391. i.vod_remarks = i.vod_remarks + '|' + it.type_name;
  392. });
  393. if (rule.search_match) {
  394. data = data.filter(item => item.vod_name && (new RegExp(KEY, 'i')).test(item.vod_name))
  395. }
  396. if (data.length > 0) {
  397. if (rule.search_pic && !data[0].vod_pic) {
  398. log(`当前搜索站点【${it.type_name}】没图片,尝试访问二级去获取图片`);
  399. let detailUrl = urls[idx].split('wd=')[0] + 'ac=detail&ids=' + data.map(k => k.vod_id.split('$')[1]).join(',');
  400. try {
  401. let detailJson = JSON.parse(request(detailUrl));
  402. log('二级数据列表元素数:' + detailJson.list.length);
  403. data.forEach((d, _seq) => {
  404. // let detailVodPic = detailJson.list[_seq].vod_pic;
  405. let detailVodPic = detailJson.list.find(vod => vod.vod_id.toString() === d.vod_id.split('$')[1]);
  406. if (detailVodPic) {
  407. Object.assign(d, {vod_pic: detailVodPic.vod_pic});
  408. }
  409. });
  410. } catch (e) {
  411. log(`强制获取网站${it.type_id}的搜索图片失败:${e.message}`);
  412. }
  413. }
  414. results = results.concat(data);
  415. }
  416. results = results.concat(data);
  417. } catch (e) {
  418. log(`请求:${it.type_id}发生错误:${e.message}`)
  419. }
  420. });
  421. }
  422. VODS = results;
  423. let t2 = new Date().getTime();
  424. // log('t2:'+t2);
  425. log(`${searchMode}搜索:${urls.length}个站耗时:${(Number(t2) - Number(t1))}ms`)
  426. }
  427. }
  428. }
  429. }),
  430. lazy: $js.toString(() => {
  431. // lazy想办法用对应的parse_url,但是有难度,暂未实现
  432. let parse_url = '';
  433. if (flag && flag.includes('|')) {
  434. let type_name = flag.split('|')[0];
  435. let current_vod = rule.classes.find(item => item.type_name === type_name);
  436. if (current_vod && current_vod.parse_url) {
  437. parse_url = current_vod.parse_url
  438. }
  439. }
  440. if (/\.(m3u8|mp4)/.test(input)) {
  441. input = {parse: 0, url: input}
  442. } else {
  443. if (parse_url.startsWith('json:')) {
  444. let purl = parse_url.replace('json:', '') + input;
  445. let html = request(purl);
  446. input = {parse: 0, url: JSON.parse(html).url}
  447. } else {
  448. input = parse_url + input;
  449. }
  450. }
  451. }),
  452. }